﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/16 10:30:47
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Macros;
using ImageK.Measure;
using ImageK.Plugin.Frame;
using ImageK.Process;
using ImageK.Text;

namespace ImageK.Plugin.Filter
{
    /// <summary>
    /// This plugin implements ImageJ's Analyze/Measure and Analyze/Set Measurements commands.
    /// </summary>
    public class Analyzer : BaseMeasurementsFilter
    {
        private static bool drawLabels = true;
        private string arg;
        private ImagePlus imp;
        private ResultsTable rt;
        private int measurements;
        private StringBuilder min, max, mean, sd;
        private bool disableReset;
        private bool resultsUpdated;

        // Order must agree with order of checkboxes in Set Measurements dialog box
        private int[] list = new int[]
        {
            AREA, MEAN, STD_DEV, MODE, MIN_MAX,
            CENTROID, CENTER_OF_MASS, PERIMETER, RECT, ELLIPSE, SHAPE_DESCRIPTORS, FERET,
            INTEGRATED_DENSITY, MEDIAN, SKEWNESS, KURTOSIS, AREA_FRACTION, STACK_POSITION,
            LIMIT, LABELS, INVERT_Y, SCIENTIFIC_NOTATION, ADD_TO_OVERLAY, NaN_EMPTY_CELLS
        };

        private const string MEASUREMENTS = "measurements";
        private const string MARK_WIDTH = "mark.width";

        private const string PRECISION = "precision";

        //private static int counter;
        private static bool unsavedMeasurements;

        // public static Color darkBlue = new Color(0, 0, 160);
        private static int systemMeasurements = Prefs.getInt(MEASUREMENTS, AREA + MEAN + MIN_MAX);

        public static int markWidth;
        public static int precision = Prefs.getInt(PRECISION, 3);
        private static float[] umeans = new float[MAX_STANDARDS];
        private static ResultsTable systemRT = new ResultsTable();
        private static int redirectTarget;
        private static String redirectTitle = "";
        private static ImagePlus redirectImage; // non-displayed images
        public static int firstParticle, lastParticle;
        private static bool switchingModes;
        private static bool showMin = true;
        private static bool showAngle = true;

        public Analyzer()
        {
            rt = systemRT;
            rt.setIsResultsTable(true);
            rt.showRowNumbers(true);
            rt.setPrecision((systemMeasurements & SCIENTIFIC_NOTATION) != 0 ? -precision : precision);
            rt.setNaNEmptyCells((systemMeasurements & NaN_EMPTY_CELLS) != 0);
            measurements = systemMeasurements;
        }

        /** Constructs a new Analyzer using the specified ImagePlus object
            and the current measurement options and default results table. */
        public Analyzer(ImagePlus imp) : this()
        {
            this.imp = imp;
        }

        /** Construct a new Analyzer using an ImagePlus object and a ResultsTable. */
        public Analyzer(ImagePlus imp, ResultsTable rt) : this(imp, Analyzer.getMeasurements(), rt)
        {
        }

        /** Construct a new Analyzer using an ImagePlus object and private
            measurement options and a ResultsTable. */
        public Analyzer(ImagePlus imp, int measurements, ResultsTable rt)
        {
            this.imp = imp;
            this.measurements = measurements;
            if (rt == null)
                rt = new ResultsTable();
            rt.setPrecision((systemMeasurements & SCIENTIFIC_NOTATION) != 0 ? -precision : precision);
            rt.setNaNEmptyCells((systemMeasurements & NaN_EMPTY_CELLS) != 0);
            this.rt = rt;
        }

        public override int setup(string arg, ImagePlus imp)
        {
            this.arg = arg;
            this.imp = imp;
            IJ.register(typeof(Analyzer));
            if (arg.Equals("set"))
            {
                doSetDialog();
                return DONE;
            }
            else if (arg.Equals("sum"))
            {
                // summarize();
                return DONE;
            }
            else if (arg.Equals("clear"))
            {
                if (IJ.MacroRunning())
                    unsavedMeasurements = false;
                // resetCounter();
                return DONE;
            }
            else
            {
                return DOES_ALL + NO_CHANGES;
            }
        }
        
        public override void run(ImageProcessor ip)
        {
            //todo:
            measure();
            Roi roi = imp.getRoi();
            if (roi == null || roi.getType() != Roi.POINT)
                displayResults();
            if ((measurements & ADD_TO_OVERLAY) != 0)
            {
                addRoiToOverlay();
            }
        }
        
        private void addRoiToOverlay()
        {
            Roi roi = imp.getRoi();
            if (roi == null)
                return;
            roi = (Roi)roi.clone();
            if (imp.getStackSize() > 1)
            {
                if (imp.isHyperStack() || imp.isComposite())
                    roi.setPosition(0, imp.getSlice(), imp.getFrame());
                else
                    roi.setPosition(imp.getCurrentSlice());
            }
        
            if (roi.getName() == null)
                roi.setName("" + rt.size());
            //roi.setName(IJ.getString("Label:", "m"+rt.size()));
            roi.setIgnoreClipRect(true);
            Overlay overlay = imp.getOverlay();
            if (overlay == null)
                overlay = new Overlay();
            if (drawLabels)
                overlay.drawLabels(true);
            if (!overlay.getDrawNames())
                overlay.drawNames(true);
            overlay.setLabelColor(Color.White);
            overlay.drawBackgrounds(true);
            overlay.add(roi);
            imp.setOverlay(overlay);
            if (roi.getType() == Roi.COMPOSITE && ToolBar.getToolId() == ToolBar.OVAL && ToolBar.getBrushSize() > 0)
                imp.deleteRoi(); // delete ROIs created with the selection brush tool
        }
        
        void doSetDialog()
        {
            string NONE = "None";
            string[] titles;
            int[] wList = WindowManager.getIDList();
            if (wList == null)
            {
                titles = new string[1];
                titles[0] = NONE;
            }
            else
            {
                titles = new string[wList.Length + 1];
                titles[0] = NONE;
                for (int i = 0; i < wList.Length; i++)
                {
                    ImagePlus imp = WindowManager.getImage(wList[i]);
                    titles[i + 1] = imp != null ? imp.getTitle() : "";
                }
            }
        
            ImagePlus tImp = WindowManager.getImage(redirectTarget);
            string target = tImp != null ? tImp.getTitle() : NONE;

            //todo:
            // string macroOptions = Macro.getOptions();
            // if (macroOptions != null && macroOptions.IndexOf("circularity ") != -1)
            //     Macro.setOptions(macroOptions.Replace("circularity ", "shape "));
            // if (macroOptions != null && macroOptions.IndexOf("slice ") != -1)
            //     Macro.setOptions(macroOptions.Replace("slice ", "stack "));
        
            GenericDialog gd = new GenericDialog("Set Measurements");
            string[] labels = new string[18];
            bool[] states = new bool[18];
            labels[0] = "Area";
            states[0] = (systemMeasurements & AREA) != 0;
            labels[1] = "Mean gray value";
            states[1] = (systemMeasurements & MEAN) != 0;
            labels[2] = "Standard deviation";
            states[2] = (systemMeasurements & STD_DEV) != 0;
            labels[3] = "Modal gray value";
            states[3] = (systemMeasurements & MODE) != 0;
            labels[4] = "Min & max gray value";
            states[4] = (systemMeasurements & MIN_MAX) != 0;
            labels[5] = "Centroid";
            states[5] = (systemMeasurements & CENTROID) != 0;
            labels[6] = "Center of mass";
            states[6] = (systemMeasurements & CENTER_OF_MASS) != 0;
            labels[7] = "Perimeter";
            states[7] = (systemMeasurements & PERIMETER) != 0;
            labels[8] = "Bounding rectangle";
            states[8] = (systemMeasurements & RECT) != 0;
            labels[9] = "Fit ellipse";
            states[9] = (systemMeasurements & ELLIPSE) != 0;
            labels[10] = "Shape descriptors";
            states[10] = (systemMeasurements & SHAPE_DESCRIPTORS) != 0;
            labels[11] = "Feret's diameter";
            states[11] = (systemMeasurements & FERET) != 0;
            labels[12] = "Integrated density";
            states[12] = (systemMeasurements & INTEGRATED_DENSITY) != 0;
            labels[13] = "Median";
            states[13] = (systemMeasurements & MEDIAN) != 0;
            labels[14] = "Skewness";
            states[14] = (systemMeasurements & SKEWNESS) != 0;
            labels[15] = "Kurtosis";
            states[15] = (systemMeasurements & KURTOSIS) != 0;
            labels[16] = "Area_fraction";
            states[16] = (systemMeasurements & AREA_FRACTION) != 0;
            labels[17] = "Stack position";
            states[17] = (systemMeasurements & STACK_POSITION) != 0;
            // gd.setInsets(0, 0, 0);
            gd.addCheckboxGroup(10, 2, labels, states);
            labels = new string[6];
            states = new bool[6];
            labels[0] = "Limit to threshold";
            states[0] = (systemMeasurements & LIMIT) != 0;
            labels[1] = "Display label";
            states[1] = (systemMeasurements & LABELS) != 0;
            labels[2] = "Invert Y coordinates";
            states[2] = (systemMeasurements & INVERT_Y) != 0;
            labels[3] = "Scientific notation";
            states[3] = (systemMeasurements & SCIENTIFIC_NOTATION) != 0;
            ;
            labels[4] = "Add to overlay";
            states[4] = (systemMeasurements & ADD_TO_OVERLAY) != 0;
            ;
            labels[5] = "NaN empty cells";
            states[5] = (systemMeasurements & NaN_EMPTY_CELLS) != 0;
            ;
            // gd.setInsets(0, 0, 0);
            // gd.addCheckboxGroup(3, 2, labels, states);
            // gd.setInsets(15, 0, 0);
            gd.addChoice("Redirect to:", titles, target);
            // gd.setInsets(5, 0, 0);
            gd.addNumericField("Decimal places (0-9):", precision, 0, 2, "");
            // gd.addHelp(IJ.URL + "/docs/menus/analyze.html#set");
            gd.showDialog();
            if (gd.wasCanceled())
                return;
            int oldMeasurements = systemMeasurements;
            setOptions(gd);
            int index = gd.getNextChoiceIndex();
            redirectTarget = index == 0 ? 0 : wList[index - 1];
            redirectTitle = titles[index];

            //todo:
            // ImagePlus imp = WindowManager.getImage(redirectTarget);
            // redirectImage = imp != null && imp.getWindow() == null ? imp : null;
        
            int prec = (int)gd.getNextNumber();
            if (prec < 0) prec = 0;
            if (prec > 9) prec = 9;
            bool notationChanged =
                (oldMeasurements & SCIENTIFIC_NOTATION) != (systemMeasurements & SCIENTIFIC_NOTATION);
            if (prec != precision || notationChanged)
            {
                precision = prec;
                rt.setPrecision((systemMeasurements & SCIENTIFIC_NOTATION) != 0 ? -precision : precision);
                if (rt.size() > 0)
                    rt.show("Results");
            }
        }
        
        void setOptions(GenericDialog gd)
        {
            int oldMeasurements = systemMeasurements;
            int previous = 0;
            bool b = false;
            for (int i = 0; i < list.Length; i++)
            {
                //if (list[i]!=previous)
                b = gd.getNextBoolean();
                previous = list[i];
                if (b)
                    systemMeasurements |= list[i];
                else
                    systemMeasurements &= ~list[i];
            }
        
            if (rt != null && rt.size() > 1 && !IJ.isResultsWindow() && IJ.GetInstance() != null)
                rt.reset();
            if ((oldMeasurements & (~SCIENTIFIC_NOTATION)) != (systemMeasurements & (~SCIENTIFIC_NOTATION)) &&
                IJ.isResultsWindow())
            {
                rt.setPrecision((systemMeasurements & SCIENTIFIC_NOTATION) != 0 ? -precision : precision);
                // clearSummary();
                rt.update(systemMeasurements, imp, null);
            }
        
            if ((systemMeasurements & LABELS) == 0)
                systemRT.disableRowLabels();
        }
        
        /** Measures the image or selection and adds the results to the default results table. */
        public void measure()
        {
            string lastHdr = rt.getColumnHeading(ResultsTable.LAST_HEADING);
            if (lastHdr == null || lastHdr[0] != 'M')
            {
                if (!reset()) return;
            }
        
            firstParticle = lastParticle = 0;
            Roi roi = imp.getRoi();
            if (roi != null && roi.getType() == Roi.POINT)
            {
                // measurePoint(roi);
                return;
            }
        
            if (roi != null && roi.isLine())
            {
                measureLength(roi);
                return;
            }
        
            if (roi != null && roi.getType() == Roi.ANGLE)
            {
                // measureAngle(roi);
                return;
            }
        
            ImageStatistics stats;
            if (isRedirectImage())
            {
                //todo:
                stats = null;
                // stats = getRedirectStats(measurements, roi);
                if (stats == null) return;
            }
            else
                stats = imp.getStatistics(measurements);
            
            if (!IJ.isResultsWindow() && IJ.GetInstance() != null)
                reset();
            saveResults(stats, roi);
        }

        bool reset()
        {
            bool ok = true;
            if (rt.size() > 0 && !disableReset)
                ok = resetCounter();
            if (ok && rt.getColumnHeading(ResultsTable.LAST_HEADING) == null)
                rt.setDefaultHeadings();
            return ok;
        }
        
        /** Returns <code>true</code> if an image is selected in the "Redirect To:"
	        popup menu of the Analyze/Set Measurements dialog box. */
        public static bool isRedirectImage()
        {
            return redirectTarget != 0;
        }
        //
        // /** Set the "Redirect To" image. Pass 'null' as the 
	       //  argument to disable redirected sampling. */
        // public static void setRedirectImage(ImagePlus imp)
        // {
        //     if (imp == null)
        //     {
        //         redirectTarget = 0;
        //         redirectTitle = null;
        //         redirectImage = null;
        //     }
        //     else
        //     {
        //         redirectTarget = imp.getID();
        //         redirectTitle = imp.getTitle();
        //         if (imp.getWindow() == null)
        //             redirectImage = imp;
        //     }
        // }
        //
        private ImagePlus getRedirectImageOrStack(ImagePlus cimp)
        {
            ImagePlus rimp = getRedirectImage(cimp);
            if (rimp != null)
            {
                int depth = rimp.getStackSize();
                if (depth > 1 && depth == cimp.getStackSize() && rimp.getCurrentSlice() != cimp.getCurrentSlice())
                    rimp.setSlice(cimp.getCurrentSlice());
            }
        
            return rimp;
        }
        
        /** Returns the image selected in the "Redirect To:" popup
	        menu of the Analyze/Set Measurements dialog, or null
	        if "None" is selected, the image was not found or the 
	        image is not the same size as <code>currentImage</code>. */
        public static ImagePlus getRedirectImage(ImagePlus cimp)
        {
            ImagePlus rimp = WindowManager.getImage(redirectTarget);
            if (rimp == null)
                rimp = redirectImage;
            if (rimp == null)
            {
                IJ.error("Analyzer", "Redirect image (\"" + redirectTitle + "\")\n"
                                     + "not found.");
                redirectTarget = 0;
                Macro.abort();
                return null;
            }
        
            if (rimp.getWidth() != cimp.getWidth() || rimp.getHeight() != cimp.getHeight())
            {
                IJ.error("Analyzer", "Redirect image (\"" + redirectTitle + "\") \n"
                                     + "is not the same size as the current image.");
                Macro.abort();
                return null;
            }
        
            return rimp;
        }
        //
        // ImageStatistics getRedirectStats(int measurements, Roi roi)
        // {
        //     ImagePlus redirectImp = getRedirectImageOrStack(imp);
        //     if (redirectImp == null)
        //         return null;
        //     ImageProcessor ip = redirectImp.getProcessor();
        //     if (imp.getTitle().equals("mask") && imp.getBitDepth() == 8)
        //     {
        //         ip.setMask(imp.getProcessor());
        //         ip.setRoi(0, 0, imp.getWidth(), imp.getHeight());
        //     }
        //     else
        //         ip.setRoi(roi);
        //
        //     return ImageStatistics.getStatistics(ip, measurements, redirectImp.getCalibration());
        // }
        //
        // void measurePoint(Roi roi)
        // {
        //     if (rt.size() > 0)
        //     {
        //         if (!IJ.isResultsWindow())
        //             reset();
        //         int index = rt.getColumnIndex("X");
        //         if ((index < 0 || !rt.columnExists(index)) && rt.getLastColumn() < ResultsTable.LAST_HEADING)
        //         {
        //             clearSummary();
        //             rt.update(measurements, imp, roi);
        //         }
        //     }
        //
        //     FloatPolygon p = roi.getFloatPolygon();
        //     ImagePlus imp2 = isRedirectImage() ? getRedirectImageOrStack(imp) : null;
        //     if (imp2 == null) imp2 = imp;
        //     ImageStack stack = null;
        //     if (imp2.getStackSize() > 1)
        //         stack = imp2.getStack();
        //     PointRoi pointRoi = roi instanceof PointRoi ? (PointRoi)roi : null;
        //     for (int i = 0; i < p.npoints; i++)
        //     {
        //         int position = 0;
        //         if (pointRoi != null)
        //             position = pointRoi.getPointPosition(i);
        //         ImageProcessor ip = null;
        //         if (stack != null && position > 0 && position <= stack.size())
        //             ip = stack.getProcessor(position);
        //         else
        //             ip = imp2.getProcessor();
        //         ip.setRoi((int)Math.round(p.xpoints[i]), (int)Math.round(p.ypoints[i]), 1, 1);
        //         ImageStatistics stats = ImageStatistics.getStatistics(ip, measurements, imp2.getCalibration());
        //         stats.xCenterOfMass = p.xpoints[i];
        //         stats.yCenterOfMass = p.ypoints[i];
        //         PointRoi point = new PointRoi(p.xpoints[i], p.ypoints[i]);
        //         point.setPosition(position);
        //         if (pointRoi != null && pointRoi.getNCounters() > 1)
        //         {
        //             int[] counters = pointRoi.getCounters();
        //             if (counters != null && i < counters.length)
        //             {
        //                 int counter = counters[i] & 0xff;
        //                 int count = pointRoi.getCount(counter);
        //                 int[] info = new int[2];
        //                 info[0] = counter;
        //                 info[1] = count;
        //                 point.setCounterInfo(info);
        //             }
        //         }
        //
        //         saveResults(stats, point);
        //         if (rt == systemRT)
        //             displayResults();
        //     }
        // }
        //
        // void measureAngle(Roi roi)
        // {
        //     if (rt.size() > 0)
        //     {
        //         if (!IJ.isResultsWindow()) reset();
        //         int index = rt.getColumnIndex("Angle");
        //         if (index < 0 || !rt.columnExists(index))
        //         {
        //             clearSummary();
        //             rt.update(measurements, imp, roi);
        //         }
        //     }
        //
        //     ImageProcessor ip = imp.getProcessor();
        //     ip.setRoi(roi.getPolygon());
        //     ImageStatistics stats = new ImageStatistics();
        //     saveResults(stats, roi);
        // }
        //
        void measureLength(Roi roi)
        {
            ImagePlus imp2 = isRedirectImage() ? getRedirectImageOrStack(imp) : null;
            if (imp2 != null)
                imp2.setRoi(roi);
            else
                imp2 = imp;
            if (rt.size() > 0)
            {
                if (!IJ.isResultsWindow()) reset();
                bool update = false;
                int index = rt.getColumnIndex("Length");
                if (index < 0 || !rt.columnExists(index))
                    update = true;
                if (roi.getType() == Roi.LINE)
                {
                    index = rt.getColumnIndex("Angle");
                    if (index < 0 || !rt.columnExists(index)) update = true;
                }
        
                if (update)
                {
                    clearSummary();
                    rt.update(measurements, imp2, roi);
                }
            }
        
            if ((measurements & (AREA + MEAN + STD_DEV + MODE + MIN_MAX + CENTROID + MEDIAN)) == 0)
            {
                incrementCounter();
                rt.addValue("Length", roi.getLength());
                if (roi.getType() == Roi.LINE && showAngle)
                {
                    Line line = (Line)roi;
                    rt.addValue("Angle", line.getFloatAngle(line.x1d, line.y1d, line.x2d, line.y2d));
                }
        
                if ((measurements & LABELS) != 0)
                    rt.addLabel("Label", getFileName());
                return;
            }
        
            bool straightLine = roi.getType() == Roi.LINE;
            int lineWidth = (int)Math.Round(roi.getStrokeWidth());
            ImageProcessor ip2 = imp2.getProcessor();
            double minThreshold = ip2.getMinThreshold();
            double maxThreshold = ip2.getMaxThreshold();
            int limit = (Analyzer.getMeasurements() & LIMIT) != 0 ? LIMIT : 0;
            bool calibrated = imp2.getCalibration().calibrated();
            Rectangle saveR = Rectangle.Empty;
            Calibration globalCal = calibrated ? imp2.getGlobalCalibration() : null;
            Calibration localCal = null;
            if (globalCal != null)
            {
                imp2.setGlobalCalibration(null);
                localCal = imp2.getCalibration().copy();
                imp2.setCalibration(globalCal);
            }
        
            if (lineWidth > 1)
            {
                saveR = ip2.getRoi();
                ip2.setRoi(Roi.convertLineToArea(roi));
            }
            else if (calibrated && limit != 0)
            {
                // Calibration cal = imp2.getCalibration().copy();
                // imp2.getCalibration().disableDensityCalibration();
                // ProfilePlot profile = new ProfilePlot(imp2);
                // imp2.setCalibration(cal);
                // double[] values = profile.getProfile();
                // if (values == null) return;
                // ip2 = new FloatProcessor(values.length, 1, values);
                // ip2 = convertToOriginalDepth(imp2, ip2);
                // ip2.setCalibrationTable(cal.getCTable());
            }
            else
            {
                // ProfilePlot profile = new ProfilePlot(imp2);
                // double[] values = profile.getProfile();
                // if (values == null) return;
                // ip2 = new FloatProcessor(values.length, 1, values);
                // if (limit != 0)
                //     ip2 = convertToOriginalDepth(imp2, ip2);
            }
        
            if (limit != 0 && minThreshold != ImageProcessor.NO_THRESHOLD)
                ip2.setThreshold(minThreshold, maxThreshold, ImageProcessor.NO_LUT_UPDATE);
            ImageStatistics stats = ImageStatistics.getStatistics(ip2,
                AREA + MEAN + STD_DEV + MODE + MIN_MAX + MEDIAN + limit, imp2.getCalibration());
            if (saveR != null)
                ip2.setRoi(saveR);
            if ((roi is Line) && (measurements & CENTROID) != 0)
            {
                FloatPolygon p = ((Line)roi).getFloatPoints();
                stats.xCentroid = p.xpoints[0] + (p.xpoints[1] - p.xpoints[0]) / 2.0;
                stats.yCentroid = p.ypoints[0] + (p.ypoints[1] - p.ypoints[0]) / 2.0;
                if (imp2 != null)
                {
                    Calibration cal = imp.getCalibration();
                    stats.xCentroid = cal.getX(stats.xCentroid);
                    stats.yCentroid = cal.getY(stats.yCentroid, imp2.getHeight());
                }
            }
        
            saveResults(stats, roi);
            if (globalCal != null && localCal != null)
            {
                imp2.setGlobalCalibration(globalCal);
                imp2.setCalibration(localCal);
            }
        }
        //
        // private ImageProcessor convertToOriginalDepth(ImagePlus imp, ImageProcessor ip)
        // {
        //     if (imp.getBitDepth() == 8)
        //         ip = ip.convertToByte(false);
        //     else if (imp.getBitDepth() == 16)
        //         ip = ip.convertToShort(false);
        //     return ip;
        // }
        //
        
        /** Saves the measurements specified in the "Set Measurements" dialog,
	        or by calling setMeasurements(), in the default results table.
        */
        public void saveResults(ImageStatistics stats, Roi roi)
        {
            //todo:
            if (rt.getColumnHeading(ResultsTable.LAST_HEADING) == null)
                reset();
            clearSummary();
            incrementCounter();
            int counter = rt.size();
            if (counter <= MAX_STANDARDS && !(stats.umean == 0.0 && counter == 1 && umeans != null && umeans[0] != 0f))
            {
                if (umeans == null) umeans = new float[MAX_STANDARDS];
                umeans[counter - 1] = (float)stats.umean;
            }
            
            if ((measurements & LABELS) != 0)
                rt.addLabel("Label", getFileName());
            if ((measurements & AREA) != 0) rt.addValue(ResultsTable.AREA, stats.area);
            if ((measurements & MEAN) != 0) rt.addValue(ResultsTable.MEAN, stats.mean);
            if ((measurements & STD_DEV) != 0) rt.addValue(ResultsTable.STD_DEV, stats.stdDev);
            if ((measurements & MODE) != 0) rt.addValue(ResultsTable.MODE, stats.dmode);
            if ((measurements & MIN_MAX) != 0)
            {
                if (showMin) rt.addValue(ResultsTable.MIN, stats.min);
                rt.addValue(ResultsTable.MAX, stats.max);
            }
            
            if ((measurements & CENTROID) != 0)
            {
                rt.addValue(ResultsTable.X_CENTROID, stats.xCentroid);
                rt.addValue(ResultsTable.Y_CENTROID, stats.yCentroid);
            }
            
            if ((measurements & CENTER_OF_MASS) != 0)
            {
                rt.addValue(ResultsTable.X_CENTER_OF_MASS, stats.xCenterOfMass);
                rt.addValue(ResultsTable.Y_CENTER_OF_MASS, stats.yCenterOfMass);
            }
        //
        //     if ((measurements & PERIMETER) != 0 || (measurements & SHAPE_DESCRIPTORS) != 0)
        //     {
        //         double perimeter;
        //         if (roi != null)
        //             perimeter = roi.getLength();
        //         else
        //             perimeter = imp != null ? imp.getWidth() * 2 + imp.getHeight() * 2 : 0.0;
        //         if ((measurements & PERIMETER) != 0)
        //             rt.addValue(ResultsTable.PERIMETER, perimeter);
        //         if ((measurements & SHAPE_DESCRIPTORS) != 0)
        //         {
        //             double circularity =
        //                 perimeter == 0.0 ? 0.0 : 4.0 * Math.PI * (stats.area / (perimeter * perimeter));
        //             if (circularity > 1.0) circularity = 1.0;
        //             rt.addValue(ResultsTable.CIRCULARITY, circularity);
        //             Polygon ch = null;
        //             boolean isArea = roi == null || roi.isArea();
        //             double convexArea = roi != null ? getArea(roi.getConvexHull()) : stats.pixelCount;
        //             rt.addValue(ResultsTable.ASPECT_RATIO, isArea ? stats.major / stats.minor : 0.0);
        //             rt.addValue(ResultsTable.ROUNDNESS,
        //                 isArea ? 4.0 * stats.area / (Math.PI * stats.major * stats.major) : 0.0);
        //             rt.addValue(ResultsTable.SOLIDITY, isArea ? stats.pixelCount / convexArea : Double.NaN);
        //             if (rt.size() == 1)
        //             {
        //                 rt.setDecimalPlaces(ResultsTable.CIRCULARITY, precision);
        //                 rt.setDecimalPlaces(ResultsTable.ASPECT_RATIO, precision);
        //                 rt.setDecimalPlaces(ResultsTable.ROUNDNESS, precision);
        //                 rt.setDecimalPlaces(ResultsTable.SOLIDITY, precision);
        //             }
        //             //rt.addValue(ResultsTable.CONVEXITY, getConvexPerimeter(roi, ch)/perimeter);
        //         }
        //     }
        //
            if ((measurements & RECT) != 0)
            {
                if (roi != null && roi.isLine())
                {
                    Rectangle bounds = roi.getBounds();
                    double rx = bounds.X;
                    double ry = bounds.Y;
                    double rw = bounds.Width;
                    double rh = bounds.Height;
                    Calibration cal = imp != null ? imp.getCalibration() : null;
                    if (cal != null)
                    {
                        rx = cal.getX(rx);
                        ry = cal.getY(ry, imp.getHeight());
                        rw *= cal.pixelWidth;
                        rh *= cal.pixelHeight;
                    }
            
                    rt.addValue(ResultsTable.ROI_X, rx);
                    rt.addValue(ResultsTable.ROI_Y, ry);
                    rt.addValue(ResultsTable.ROI_WIDTH, rw);
                    rt.addValue(ResultsTable.ROI_HEIGHT, rh);
                }
                else
                {
                    rt.addValue(ResultsTable.ROI_X, stats.roiX);
                    rt.addValue(ResultsTable.ROI_Y, stats.roiY);
                    rt.addValue(ResultsTable.ROI_WIDTH, stats.roiWidth);
                    rt.addValue(ResultsTable.ROI_HEIGHT, stats.roiHeight);
                }
            }
            
            if ((measurements & ELLIPSE) != 0)
            {
                rt.addValue(ResultsTable.MAJOR, stats.major);
                rt.addValue(ResultsTable.MINOR, stats.minor);
                rt.addValue(ResultsTable.ANGLE, stats.angle);
            }
        //
        //     if ((measurements & FERET) != 0)
        //     {
        //         bool extras = true;
        //         double FeretDiameter = Double.NaN,
        //             feretAngle = Double.NaN,
        //             minFeret = Double.NaN,
        //             feretX = Double.NaN,
        //             feretY = Double.NaN;
        //         Roi roi2 = roi;
        //         if (roi2 == null && imp != null)
        //             roi2 = new Roi(0, 0, imp.getWidth(), imp.getHeight());
        //         if (roi2 != null)
        //         {
        //             double[] a = roi2.getFeretValues();
        //             if (a != null)
        //             {
        //                 FeretDiameter = a[0];
        //                 feretAngle = a[1];
        //                 minFeret = a[2];
        //                 feretX = a[3];
        //                 feretY = a[4];
        //             }
        //         }
        //
        //         rt.addValue(ResultsTable.FERET, FeretDiameter);
        //         rt.addValue(ResultsTable.FERET_X, feretX);
        //         rt.addValue(ResultsTable.FERET_Y, feretY);
        //         rt.addValue(ResultsTable.FERET_ANGLE, feretAngle);
        //         rt.addValue(ResultsTable.MIN_FERET, minFeret);
        //     }
        //
            if ((measurements & INTEGRATED_DENSITY) != 0)
            {
                rt.addValue(ResultsTable.INTEGRATED_DENSITY, stats.area * stats.mean);
                rt.addValue(ResultsTable.RAW_INTEGRATED_DENSITY, stats.pixelCount * stats.umean);
            }
        //
        //     if ((measurements & MEDIAN) != 0) rt.addValue(ResultsTable.MEDIAN, stats.median);
        //     if ((measurements & SKEWNESS) != 0) rt.addValue(ResultsTable.SKEWNESS, stats.skewness);
        //     if ((measurements & KURTOSIS) != 0) rt.addValue(ResultsTable.KURTOSIS, stats.kurtosis);
        //     if ((measurements & AREA_FRACTION) != 0) rt.addValue(ResultsTable.AREA_FRACTION, stats.areaFraction);
        //     if ((measurements & STACK_POSITION) != 0)
        //     {
        //         boolean update = false;
        //         if (imp != null && (imp.isHyperStack() || imp.isComposite()))
        //         {
        //             int[] position = imp.convertIndexToPosition(imp.getCurrentSlice());
        //             if (imp.getNChannels() > 1)
        //             {
        //                 int index = rt.getColumnIndex("Ch");
        //                 if (index < 0 || !rt.columnExists(index)) update = true;
        //                 rt.addValue("Ch", position[0]);
        //             }
        //
        //             if (imp.getNSlices() > 1)
        //             {
        //                 int index = rt.getColumnIndex("Slice");
        //                 if (index < 0 || !rt.columnExists(index)) update = true;
        //                 rt.addValue("Slice", position[1]);
        //             }
        //
        //             if (imp.getNFrames() > 1)
        //             {
        //                 int index = rt.getColumnIndex("Frame");
        //                 if (index < 0 || !rt.columnExists(index)) update = true;
        //                 rt.addValue("Frame", position[2]);
        //             }
        //         }
        //         else
        //         {
        //             int index = rt.getColumnIndex("Slice");
        //             if (index < 0 || !rt.columnExists(index)) update = true;
        //             rt.addValue("Slice", imp != null ? imp.getCurrentSlice() : 1.0);
        //         }
        //
        //         if (update && rt == systemRT && IJ.isResultsWindow())
        //             rt.update(measurements, imp, roi);
        //     }
        //
            if (roi != null)
            {
                if (roi.isLine())
                {
                    rt.addValue("Length", roi.getLength());
                    if (roi.getType() == Roi.LINE && showAngle)
                    {
                        Line line = (Line)roi;
                        rt.addValue("Angle", line.getFloatAngle(line.x1d, line.y1d, line.x2d, line.y2d));
                    }
                }
                else if (roi.getType() == Roi.ANGLE)
                {
                    // double angle = ((PolygonRoi)roi).getAngle();
                    // if (Prefs.reflexAngle) angle = 360.0 - angle;
                    // rt.addValue("Angle", angle);
                }
                else if (roi is PointRoi)
                {
                    //todo:
                    // savePoints((PointRoi)roi);
                }
            }
        
            if ((measurements & LIMIT) != 0 && imp != null && imp.getBitDepth() != 24)
            {
                rt.addValue(ResultsTable.MIN_THRESHOLD, stats.lowerThreshold);
                rt.addValue(ResultsTable.MAX_THRESHOLD, stats.upperThreshold);
            }
            
            if (roi is RotatedRectRoi)
            {
                double[] p = ((RotatedRectRoi)roi).getParams();
                double dx = p[2] - p[0];
                double dy = p[3] - p[1];
                double length = Math.Sqrt(dx * dx + dy * dy);
                Calibration cal = imp != null ? imp.getCalibration() : null;
                double pw = 1.0;
                if (cal != null && cal.pixelWidth == cal.pixelHeight)
                    pw = cal.pixelWidth;
                rt.addValue("RRLength", length * pw);
                rt.addValue("RRWidth", p[4] * pw);
            }
            
            int group = roi != null ? roi.getGroup() : 0;
            if (group > 0)
            {
                rt.addValue("Group", group);
                String name = Roi.getGroupName(group);
                if (name != null)
                    rt.addValue("GroupName", name);
            }
        }
        
        private void clearSummary()
        {
            if (rt.size() >= 4 && "Max".Equals(rt.getLabel(rt.size() - 1)))
            {
                for (int i = 0; i < 4; i++)
                    rt.deleteRow(rt.size() - 1);
                rt.show("Results");
            }
        }
        //
        // double getArea(Polygon p)
        // {
        //     if (p == null) return Double.NaN;
        //     int carea = 0;
        //     int iminus1;
        //     for (int i = 0; i < p.npoints; i++)
        //     {
        //         iminus1 = i - 1;
        //         if (iminus1 < 0) iminus1 = p.npoints - 1;
        //         carea += (p.xpoints[i] + p.xpoints[iminus1]) * (p.ypoints[i] - p.ypoints[iminus1]);
        //     }
        //
        //     return (Math.Abs(carea / 2.0));
        // }
        //
        // void savePoints(PointRoi roi)
        // {
        //     if (imp == null)
        //     {
        //         rt.addValue("X", 0.0);
        //         rt.addValue("Y", 0.0);
        //         return;
        //     }
        //
        //     if ((measurements & AREA) != 0)
        //         rt.addValue(ResultsTable.AREA, 0);
        //     FloatPolygon p = roi.getFloatPolygon();
        //     ImageProcessor ip = imp.getProcessor();
        //     Calibration cal = imp.getCalibration();
        //     double x = p.xpoints[0];
        //     double y = p.ypoints[0];
        //     int ix = (int)x, iy = (int)y;
        //     double value = ip.getPixelValue(ix, iy);
        //     if (markWidth > 0 && !Toolbar.getMultiPointMode())
        //     {
        //         ip.setColor(Toolbar.getForegroundColor());
        //         ip.setLineWidth(markWidth);
        //         ip.moveTo(ix, iy);
        //         ip.lineTo(ix, iy);
        //         imp.updateAndDraw();
        //         ip.setLineWidth(Line.getWidth());
        //     }
        //
        //     rt.addValue("X", cal.getX(x));
        //     rt.addValue("Y", cal.getY(y, imp.getHeight()));
        //     int position = roi.getPosition();
        //     if (imp.isHyperStack() || imp.isComposite())
        //     {
        //         int channel = imp.getChannel();
        //         int slice = imp.getSlice();
        //         int frame = imp.getFrame();
        //         if (position > 0)
        //         {
        //             int[] pos = imp.convertIndexToPosition(position);
        //             channel = pos[0];
        //             slice = pos[1];
        //             frame = pos[2];
        //         }
        //
        //         if (imp.getNChannels() > 1)
        //             rt.addValue("Ch", channel);
        //         if (imp.getNSlices() > 1)
        //             rt.addValue("Slice", slice);
        //         if (imp.getNFrames() > 1)
        //             rt.addValue("Frame", frame);
        //     }
        //     else if (imp.getStackSize() > 1)
        //     {
        //         if (position == 0)
        //             position = imp.getCurrentSlice();
        //         rt.addValue("Slice", position);
        //     }
        //
        //     int[] info = roi.getCounterInfo();
        //     if (info != null)
        //     {
        //         rt.addValue("Counter", info[0]);
        //         rt.addValue("Count", info[1]);
        //     }
        //
        //     if (imp.getProperty("FHT") != null)
        //     {
        //         double center = imp.getWidth() / 2.0;
        //         y = imp.getHeight() - y - 1;
        //         double r = Math.Sqrt((x - center) * (x - center) + (y - center) * (y - center));
        //         if (r < 1.0) r = 1.0;
        //         double theta = Math.atan2(y - center, x - center);
        //         theta = theta * 180.0 / Math.PI;
        //         if (theta < 0) theta = 360.0 + theta;
        //         rt.addValue("R", (imp.getWidth() / r) * cal.pixelWidth);
        //         rt.addValue("Theta", theta);
        //     }
        // }
        //
        string getFileName()
        {
            string s = "";
            if (imp != null)
            {
                if (redirectTarget != 0)
                {
                    ImagePlus rImp = WindowManager.getImage(redirectTarget);
                    if (rImp == null) rImp = redirectImage;
                    if (rImp != null) s = rImp.getTitle();
                }
                else
                    s = imp.getTitle();
        
                //int len = s.length();
                //if (len>4 && s.charAt(len-4)=='.' && !Character.isDigit(s.charAt(len-1)))
                //	s = s.substring(0,len-4); 
                Roi roi = imp.getRoi();
                string roiName = roi != null ? roi.getName() : null;
                if (roiName != null && !roiName.Contains("."))
                {
                    if (roiName.Length > 30)
                        roiName = roiName.Substring(0, 27) + "...";
                    s += ":" + roiName;
                }
        
                if (imp.getStackSize() > 1)
                {
                    ImageStack stack = imp.getStack();
                    int currentSlice = imp.getCurrentSlice();
                    string label = stack.getShortSliceLabel(currentSlice);
                    string colon = s.Equals("") ? "" : ":";
                    if (label != null && !label.Equals(""))
                        s += colon + label;
                    else
                        s += colon + currentSlice;
                }
            }
        
            return s;
        }
        
        /** Writes the last row in the system results table to the Results window. */
        public void displayResults()
        {
            if (rt.columnDeleted())
                return;
            int counter = rt.size();
            if (counter == 1)
                IJ.setColumnHeadings(rt.getColumnHeadings());
            TextPanel tp = IJ.isResultsWindow() ? IJ.getTextPanel() : null;
            int lineCount = tp != null ? IJ.getTextPanel().getLineCount() : 0;
            if (counter > lineCount + 1)
            {
                // delete rt rows added by particle analyzer
                int n = counter - lineCount - 1;
                int index = lineCount;
                for (int i = 0; i < n; i++)
                    rt.deleteRow(index);
                counter = rt.size();
            }

            if (!resultsUpdated && counter > 1 && rt.getColumnIndex("Group") >= 0 &&
                rt.getValue("Group", counter - 1) > 0)
            {
                rt.show("Results");
                resultsUpdated = true;
            }
            else
                IJ.write(rt.getRowAsString(counter - 1));
        }
        //
        // /** Redisplays the results table. */
        // public void updateHeadings()
        // {
        //     rt.show("Results");
        // }
        //
        // /** Converts a number to a formatted string with a tab at the end. */
        // public string n(double n)
        // {
        //     string s;
        //     if (Math.Round(n) == n)
        //         s = ResultsTable.d2s(n, 0);
        //     else
        //         s = ResultsTable.d2s(n, precision);
        //     return s + "\t";
        // }
        
        void incrementCounter()
        {
            if (rt == null) rt = systemRT;
            rt.incrementCounter();
            unsavedMeasurements = true;
        }
        //
        // public void summarize()
        // {
        //     if (rt == null || "Max".equals(rt.getLabel(rt.size() - 1)))
        //         return;
        //     int n = rt.size();
        //     if (n < 2)
        //         return;
        //     String[] headings = rt.getHeadings();
        //     int columns = headings.length;
        //     if (columns == 0)
        //         return;
        //     int first = "Label".equals(headings[0]) ? 1 : 0;
        //     double[] min = new double[columns];
        //     double[] max = new double[columns];
        //     double[] sum = new double[columns];
        //     double[] sum2 = new double[columns];
        //     for (int i = 0; i < columns; i++)
        //     {
        //         min[i] = Double.MAX_VALUE;
        //         max[i] = -Double.MAX_VALUE;
        //     }
        //
        //     for (int row = 0; row < n; row++)
        //     {
        //         for (int col = first; col < columns; col++)
        //         {
        //             double v = rt.getValue(headings[col], row);
        //             if (v < min[col]) min[col] = v;
        //             if (v > max[col]) max[col] = v;
        //             sum[col] += v;
        //             sum2[col] += v * v;
        //         }
        //     }
        //
        //     rt.incrementCounter();
        //     rt.setLabel("Mean", n + 0);
        //     rt.incrementCounter();
        //     rt.setLabel("SD", n + 1);
        //     rt.incrementCounter();
        //     rt.setLabel("Min", n + 2);
        //     rt.incrementCounter();
        //     rt.setLabel("Max", n + 3);
        //     for (int col = first; col < columns; col++)
        //     {
        //         rt.setValue(headings[col], n + 0, sum[col] / n);
        //         rt.setValue(headings[col], n + 1, Math.sqrt((sum2[col] - sum[col] * sum[col] / n) / (n - 1)));
        //         rt.setValue(headings[col], n + 2, min[col]);
        //         rt.setValue(headings[col], n + 3, max[col]);
        //     }
        //
        //     rt.show(rt.getTitle());
        // }
        //
        /** Returns the current measurement count. */
        public static int getCounter()
        {
            return systemRT.size();
        }
        
        /** Sets the measurement counter to zero. Displays a dialog that
	        allows the user to save any existing measurements. Returns
	        false if the user cancels the dialog.
        */
        public static bool resetCounter()
        {
            //todo:
            TextPanel tp = IJ.isResultsWindow() ? IJ.getTextPanel() : null;
            int counter = systemRT.size();
            int lineCount = tp != null ? IJ.getTextPanel().getLineCount() : 0;
            ImageK ij = IJ.GetInstance();
            bool macro = (IJ.MacroRunning() && !switchingModes) || Interpreter.isBatchMode();
            switchingModes = false;
            if (counter > 0 && lineCount > 0 && unsavedMeasurements && !macro && ij != null && !ij.quitting())
            {
                var dResult = MessageBox.Show("ImageJ", "Save " + counter + " measurements?",
                    MessageBoxButtons.YesNoCancel);
                // YesNoCancelDialog d = new YesNoCancelDialog(ij, "ImageJ", "Save " + counter + " measurements?");
                if (dResult == DialogResult.Cancel)
                    return false;
                else if (dResult == DialogResult.Yes)
                {
                    if (!(new MeasurementsWriter()).save(""))
                        return false;
                }
            }
            
            umeans = null;
            systemRT.reset();
            RoiManager.resetMultiMeasureResults();
            unsavedMeasurements = false;
            if (tp != null) tp.clear();
            return true;
        }
        
        public static void setUnsavedMeasurements(bool b)
        {
            unsavedMeasurements = b;
        }
        
        // Returns the measurement options defined in the Set Measurements dialog. */
        public static int getMeasurements()
        {
            return systemMeasurements;
        }
        //
        // /** Sets the system-wide measurement options. */
        // public static void setMeasurements(int measurements)
        // {
        //     systemMeasurements = measurements;
        // }
        //
        // /** Sets the specified system-wide measurement option. */
        // public static void setMeasurement(int option, boolean state)
        // {
        //     if (state)
        //     {
        //         systemMeasurements |= option;
        //         if ((option & ADD_TO_OVERLAY) != 0)
        //             drawLabels = true;
        //     }
        //     else
        //         systemMeasurements &= ~option;
        // }
        //
        // /** Called once when ImageJ quits. */
        // public static void savePreferences(Properties prefs)
        // {
        //     prefs.put(MEASUREMENTS, Integer.toString(systemMeasurements));
        //     //prefs.put(MARK_WIDTH, Integer.toString(markWidth));
        //     prefs.put(PRECISION, Integer.toString(precision));
        // }
        //
        // /** Returns an array containing the first 20 uncalibrated means. */
        // public static float[] getUMeans()
        // {
        //     return umeans;
        // }
        //
        /** Returns the default results table. This table should only
	        be displayed in a the "Results" window. */
        public static ResultsTable getResultsTable()
        {
            systemRT.showRowNumbers(true);
            return systemRT;
        }
        
        /** Returns the number of digits displayed to the right of decimal point. */
        public static int getPrecision()
        {
            return precision;
        }
        //
        // /** Sets the number of digits displayed to the right of decimal point. */
        // public static void setPrecision(int decimalPlaces)
        // {
        //     if (decimalPlaces < 0) decimalPlaces = 0;
        //     if (decimalPlaces > 9) decimalPlaces = 9;
        //     precision = decimalPlaces;
        // }
        //
        /** Returns an updated Y coordinate based on
	        the current "Invert Y Coordinates" flag. */
        public static int updateY(int y, int imageHeight)
        {
            if ((systemMeasurements & INVERT_Y) != 0)
                y = imageHeight - y - 1;
            return y;
        }
        
        /** Returns an updated Y coordinate based on
	        the current "Invert Y Coordinates" flag. */
        public static double updateY(double y, int imageHeight)
        {
            if ((systemMeasurements & INVERT_Y) != 0)
                y = imageHeight - y - 1;
            return y;
        }
        //
        // /** Sets the default headings ("Area", "Mean", etc.). */
        // public static void setDefaultHeadings()
        // {
        //     systemRT.setDefaultHeadings();
        // }
        //
        // public static void setOption(String option, boolean b)
        // {
        //     if (option.contains("min"))
        //         showMin = b;
        //     else if (option.contains("angle"))
        //         showAngle = b;
        // }
        //
        // public static bool addToOverlay()
        // {
        //     return ((getMeasurements() & ADD_TO_OVERLAY) != 0);
        // }
        //
        public static void setResultsTable(ResultsTable rt)
        {
            TextPanel tp = IJ.isResultsWindow() ? IJ.getTextPanel() : null;
            if (tp != null)
                tp.clear();
            if (rt == null)
                rt = new ResultsTable();
            rt.setPrecision((systemMeasurements & SCIENTIFIC_NOTATION) != 0 ? -precision : precision);
            rt.setNaNEmptyCells((systemMeasurements & NaN_EMPTY_CELLS) != 0);
            systemRT = rt;
            umeans = null;
            unsavedMeasurements = false;
        }
        //
        // public static void drawLabels(bool b)
        // {
        //     drawLabels = b;
        // }
        //
        // /** Used by RoiManager.multiMeasure() to suppress save as dialogs. */
        // public void disableReset(bool b)
        // {
        //     disableReset = b;
        // }
    }
}
